from neural_net import NeuralNetwork
from neural_net import Node

def UpdateInputWeights(node, delta, learning_rate):
  # printf("delta: %f, learning_rate: %f\n", delta, learning_rate);
  for (input, weight) in zip(node.inputs, node.weights):
    weight.value += input.transformed_value * delta * learning_rate
  node.fixed_weight.value += 1 * delta * learning_rate

def FeedForward(network, input):
  network.CheckComplete()
  assert len(input.values) == len(network.inputs), 'Input values mismatch %d vs. %d' % (
    len(input.values), len(network.inputs))
  for (input, value) in zip(network.inputs, input.values):
    input.transformed_value = value

  # Fill in this function
  for node in network.hidden_nodes:
    node.raw_value = NeuralNetwork.ComputeRawValue(node)
    node.transformed_value = NeuralNetwork.Sigmoid(node.raw_value)

  for node in network.outputs:
    node.raw_value = NeuralNetwork.ComputeRawValue(node)
    node.transformed_value = NeuralNetwork.Sigmoid(node.raw_value)

def Backprop(network, input, target, learning_rate):
  network.CheckComplete()
  FeedForward(network, input);
  node_to_delta = {}
  assert len(network.outputs) == len(target.values), (
      '# of outputs in network is not equal to number of target outputs %d vs. %d' %
      (len(network.outputs), len(target.values)))
  # First we get all the deltas together, and then we update the weights.
  for (output, target) in zip(network.outputs, target.values):
    delta = (target - output.transformed_value) * NeuralNetwork.SigmoidPrime(output.raw_value)
    """
    print "diff: %f, raw: %f, sigmoid_prime: %f, delta: %f" % (
           target - output.transformed_value,
           output.raw_value,
           NeuralNetwork.SigmoidPrime(output.raw_value),
           delta)"""
    node_to_delta[output] = delta;
  hidden_range = range(len(network.hidden_nodes))
  hidden_range.reverse()
  for i in hidden_range:
    node = network.hidden_nodes[i]
    delta = 0.0
    for (neighbor, weight) in zip(node.forward_neighbors, node.forward_weights):
      assert neighbor in node_to_delta
      delta += weight.value * node_to_delta[neighbor]
    delta *= NeuralNetwork.SigmoidPrime(node.raw_value)
    node_to_delta[node] = delta;
    """
    printf("raw_value: %f, sigmoid_prime: %f, delta: %f\n",
           node->raw_value_,
           SigmoidPrime(node->raw_value_),
           delta);"""

  # Now that we have all the deltas, we can go ahead and update all the weights.
  for node in network.outputs:
    UpdateInputWeights(node, node_to_delta[node],  learning_rate);
  for node in network.hidden_nodes:
    UpdateInputWeights(node, node_to_delta[node],  learning_rate);

def Train(network, inputs, targets, learning_rate, epochs):
  network.CheckComplete()
  # Fill in this function
  for i in range(epochs):
    for (input, target) in zip(inputs, targets):
      Backprop(network, input, target, learning_rate);
